﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;

using MyLibrary.Tools;

namespace MyLibrary.IO
{
    public delegate void WriteFileOverEventHandler(long cur,long total);
    /// <summary>
    /// 使用IO的方式进行文件传输
    /// </summary>
    public class TransportFileByIO : IDisposable
    {
        public event WriteFileOverEventHandler OnWriteFileOver;
        public event WriteFileOverEventHandler OnApplySpace;

        public static long _curProcess = 0;

        FileStream gfs = null;
        object _syncObj = new object();
        string _sourceFile = String.Empty;
        string _targetFile = String.Empty;
        int _threadNumber = 0;
        AutoResetEvent[] resetEvent = null;

        public TransportFileByIO(string sourceFile,string targeFile,int threadNumber)
        {
            if (String.IsNullOrEmpty(sourceFile) || String.IsNullOrEmpty(targeFile))
                throw new ArgumentNullException("目标文件或源文件为空");
            //初始化变量
            this._sourceFile = sourceFile;
            this._targetFile = targeFile;
            this._threadNumber = threadNumber;

            this.resetEvent = new AutoResetEvent[this._threadNumber];
        }

        ~TransportFileByIO()
        {
            dispose(false);
        }

        public void TransportFileByLocalNet()
        {
            FileInfo fi = new FileInfo(this._sourceFile);
            if (!fi.Exists)
                throw new FileNotFoundException("无法找到源文件");
            long fileLength = fi.Length;

            FileInfo fi2 = new FileInfo(this._targetFile);
            ulong freeSpace=0,totalSpace=0;
            DiskInfo.GetDiskSpaceInfo(fi2.DirectoryName, out freeSpace, out totalSpace);
            if ((long)freeSpace <= fileLength)
                throw new ArgumentException("目标磁盘空间不足");
            //删除原目标文件
            File.Delete(_targetFile);

            //为文件预分配空间
            gfs = new FileStream(_targetFile, FileMode.Create, FileAccess.Write);
            Console.WriteLine("正在分配空间....");
            byte[] blank = new byte[10485760];
            long writeBlankTimes = Convert.ToInt64(Math.Floor((double)fileLength / 10485760)) + 1;
            for (long count = 0; count < writeBlankTimes; count++)
            {
                if (count == writeBlankTimes - 1)
                    gfs.Write(blank, 0, (int)(fileLength - (writeBlankTimes - 1) * 10485760));
                else
                    gfs.Write(blank, 0, blank.Length);
                if (this.OnApplySpace == null)
                    Console.Write((char)13 + "分配进度:" + (count + 1) + "/" + writeBlankTimes);
                else
                    this.OnApplySpace(count + 1, writeBlankTimes);
            }
            Console.WriteLine("");
            gfs.Seek(0, SeekOrigin.Begin);
            //计算线程的读取文件的起始和结束位置
            long threadReadLength = Convert.ToInt64(Math.Floor((double)fileLength / _threadNumber));
            Thread[] threads = new Thread[_threadNumber];
            string arg = String.Empty;
            for (int i = 0; i < _threadNumber; i++)
            {
                if(i == _threadNumber - 1)
                    arg = String.Format("{0}|{1}|{2}|{3}", fileLength, threadReadLength * i, fileLength, i);
                else
                    arg = String.Format("{0}|{1}|{2}|{3}", fileLength, threadReadLength * i, threadReadLength * (i + 1), i);

                threads[i] = new Thread(new ParameterizedThreadStart(ReadFile));
                threads[i].IsBackground = true;
                threads[i].Name = (i + 100).ToString();
                resetEvent[i] = new AutoResetEvent(false);
                threads[i].Start(arg);
            }

            WaitHandle.WaitAll(this.resetEvent);
            gfs.Flush();
            Console.WriteLine("/n文件写入完成");
        }

        /// <summary>
        /// 读取文件并写入
        /// </summary>
        /// <param name="arg">参数</param>
        private void ReadFile(object arg)
        {
            string[] args = arg.ToString().Split('|');
            long total = Convert.ToInt64(args[0]);
            long start = Convert.ToInt64(args[1]);
            long end = Convert.ToInt64(args[2]);
            int id = Convert.ToInt32(args[3]);
            byte[] readBuffer = new byte[20480];
            int readTime = (int)Math.Floor((double)(end - start) / 20480) + 1;
            int writeByte = 0;

            FileStream fs = new FileStream(_sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read);
            //跳跃到开始读取的位置
            fs.Seek(start, SeekOrigin.Begin);

            for (int i = 0; i < readTime; i++)
            {
                if (fs.Position + 20480 <= end)
                {
                    writeByte = readBuffer.Length;
                    fs.Read(readBuffer, 0, writeByte);
                }
                else
                {
                    writeByte = (int)(end - fs.Position);
                    fs.Read(readBuffer, 0, writeByte);
                }

                lock (_syncObj)
                {
                    gfs.Seek(start, SeekOrigin.Begin);
                    gfs.Write(readBuffer, 0, writeByte);
                    start += writeByte;
                    _curProcess += writeByte;
                    if(this.OnWriteFileOver != null)
                        this.OnWriteFileOver(_curProcess, total);
                }
            }
            resetEvent[id].Set();
        }

        #region IDisposable Members

        public void Dispose()
        {
            dispose(true);
        }

        private void dispose(bool disposing)
        {
            if (disposing)
            {
                if (gfs != null)
                {
                    gfs.Close();
                    gfs.Dispose();
                }
                if (resetEvent != null)
                {
                    for (int i = 0; i < resetEvent.Length; i++)
                    {
                        resetEvent[i].Close();
                        resetEvent[i] = null;
                    }
                }
            }
        }

        #endregion
    }
}
